package com.example.securitydemo.config;

import com.example.securitydemo.security.auth.ldap.LDAPMultiAuthenticationProvider;
import com.example.securitydemo.security.auth.ldap.LDAPUserRepo;
import com.example.securitydemo.security.filter.RestAuthenticationFilter;
import com.example.securitydemo.security.jwt.JwtFilter;
import com.example.securitydemo.security.userdetails.UserDetailsPasswordServiceImpl;
import com.example.securitydemo.security.userdetails.UserDetailsServiceImpl;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.core.env.Environment;
import org.springframework.core.env.Profiles;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.DelegatingPasswordEncoder;
import org.springframework.security.crypto.password.MessageDigestPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;

import java.util.Arrays;
import java.util.Collections;
import java.util.Map;

@Slf4j
@RequiredArgsConstructor
@EnableWebSecurity(debug = true)
@Import(SecurityProblemSupport.class)
@Order(99) // @Order 数值越小越优先执行
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final ObjectMapper objectMapper;
    private final SecurityProblemSupport securityProblemSupport;
    private final UserDetailsServiceImpl userDetailsService;
    private final UserDetailsPasswordServiceImpl userDetailsPasswordService;
    private final LDAPUserRepo ldapUserRepo;
    private final JwtFilter jwtFilter;
    private final Environment environment;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .requestMatchers(req -> req
                        .mvcMatchers("/authorize/**", "/admin/**", "/api/**"))

                // 配置跨域
//                .cors(corsConfigurer -> corsConfigurer
//                        .configurationSource(corsConfigurationSource()))

                // 设置session无状态
                .sessionManagement(sessionManagement -> sessionManagement
                        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // 无状态

                // 异常处理交给Problem
                .exceptionHandling(exp -> exp
                        .authenticationEntryPoint(securityProblemSupport)
                        .accessDeniedHandler(securityProblemSupport))

                .authorizeRequests(authorizeRequests -> authorizeRequests
                        .antMatchers("/authorize/**").authenticated()
                        .antMatchers("/admin/**").hasRole("ADMIN")
                        .antMatchers("/api/users/**").access("hasRole('ADMIN') or hasRole('USER')")
//                        .antMatchers("/api/users/{username}")
//                        .access("hasRole('ADMIN') or authentication.name.equals('#username')")
//                        .antMatchers("/api/**").hasRole("USER")
                        .anyRequest().authenticated())

                // addFilterAt(newFilter, oldFilter): 替换 filter
                // addFilterAfter(newFilter, afterFilter): newFilter 在 afterFilter 之后加入
                // .addFilterBefore(newFilter, beforeFilter): newFilter 在 beforeFilter 之前加入

                // 添加jwt过滤器
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)

                // 替换 security 自带的用户验证 filter
//                .addFilterAt(restAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)

                .csrf(AbstractHttpConfigurer::disable)
//                .formLogin(AbstractHttpConfigurer::disable)
//                .csrf(Customizer.withDefaults()) // 不能用这种方式，因为这方式需要携带cookies。但是现在前后端分离是不携带cookies

                // 启用 http basic ，就可以发送 http 时，携带 header
                .httpBasic(Customizer.withDefaults())
        ;
    }

    /**
     * 这里做一些请求不需要进入过滤器链中过滤，直接允许访问
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web
                .ignoring()
                .mvcMatchers(
                        "/error",
                        "/h2-console/**",
                        "/authorize/**"
                );
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 配置 LdapAuthenticationProvider
        auth.authenticationProvider(new LDAPMultiAuthenticationProvider(ldapUserRepo));
        // 配置 DaoAuthenticationProvider
/** 方案一：        auth
                .userDetailsService(userDetailsService) // 配置 AuthenticationManager 使用 userService
                .passwordEncoder(passwordEncoder())
                .userDetailsPasswordManager(userDetailsPasswordService) // 配置密码自动升级服务
**/
        /**
         * 方案二
         */
        auth.authenticationProvider(daoAuthenticationProvider());
    }

    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        val daoAuthenticationProvider = new DaoAuthenticationProvider();
        daoAuthenticationProvider.setUserDetailsService(userDetailsService); // 配置 AuthenticationManager 使用 userService
        daoAuthenticationProvider.setPasswordEncoder(passwordEncoder());
        daoAuthenticationProvider.setUserDetailsPasswordService(userDetailsPasswordService); // 配置密码自动升级服务
        return daoAuthenticationProvider;
    }

    @Bean
    public LDAPMultiAuthenticationProvider ldapMultiAuthenticationProvider() {
        return new LDAPMultiAuthenticationProvider(ldapUserRepo);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 自定义认证过滤器
     * @return
     * @throws Exception
     */
    private RestAuthenticationFilter restAuthenticationFilter() throws Exception {
        RestAuthenticationFilter filter = new RestAuthenticationFilter(objectMapper);
        filter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler());
        filter.setAuthenticationFailureHandler(jsonAuthenticationFailureHandler());
        // authenticationManager() 是父类的方法
        filter.setAuthenticationManager(authenticationManager());
        filter.setFilterProcessesUrl("/authorize/login");
        return filter;
    }

    // 认证失败处理
    private AuthenticationFailureHandler jsonAuthenticationFailureHandler() {
        return (req, res, exception) -> {
            // val objectMapper 相当于 final ObjectMapper objectMapper
            // val 是 lombok 的
            val objectMapper = new ObjectMapper();
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            res.setContentType(MediaType.APPLICATION_JSON_VALUE);
            res.setCharacterEncoding("UTF-8");
            final Map<String, String> errData = Map.of(
                    "title", "认证失败",
                    "details", exception.getMessage()
            );
            res.getWriter().println(objectMapper.writeValueAsString(errData));
            log.info("认证失败");
        };
    }

    // 认证成功处理
    private AuthenticationSuccessHandler jsonAuthenticationSuccessHandler() {
        return (req, res, auth) -> {
            ObjectMapper objectMapper = new ObjectMapper();
            res.setStatus(HttpStatus.OK.value());
            res.getWriter().println(objectMapper.writeValueAsString(auth));
            log.debug("认证成功");
        };
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
//        return new BCryptPasswordEncoder();
//        return new Pbkdf2PasswordEncoder();
        // 多种编码形式 兼容新版和旧版加密算法
        val idForEncode = "bcrypt";
        val encoders = Map.of(
                idForEncode, new BCryptPasswordEncoder(),
                "SHA-1", new MessageDigestPasswordEncoder("SHA-1")
        );
        return new DelegatingPasswordEncoder(idForEncode, encoders);
    }

    /**
     * 我们在 Spring Boot 中有几种其他方式配置 CORS
     * 参见 https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-cors
     * Mvc 的配置方式见 WebMvcConfig 中的代码
     *
     * @return CorsConfigurationSource
     */
//    @Bean
//    CorsConfigurationSource corsConfigurationSource() {
//        CorsConfiguration configuration = new CorsConfiguration();
//        configuration.addAllowedOrigin("*");
//        configuration.addAllowedHeader("*");
//        configuration.addAllowedMethod("*");
////        configuration.setAllowedOrigins(Collections.singletonList("*"));
////        configuration.setAllowedMethods(Arrays.asList("*"));
////        configuration.setAllowedHeaders(Collections.singletonList("*"));
//        configuration.addExposedHeader("X-Authenticate");
//        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//        source.registerCorsConfiguration("/**", configuration);
//        return source;
//    }
//    @Bean
//    CorsConfigurationSource corsConfigurationSource() {
//        CorsConfiguration configuration = new CorsConfiguration();
//        // 允许跨域访问的主机
//        if (environment.acceptsProfiles(Profiles.of("dev"))) {
//            configuration.setAllowedOrigins(Collections.singletonList("*"));
//        } else {
//            configuration.setAllowedOrigins(Collections.singletonList("http://uaa.imooc.com"));
//        }
//        configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
//        configuration.setAllowedHeaders(Collections.singletonList("*"));
//        configuration.addExposedHeader("X-Authenticate");
//        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
//        source.registerCorsConfiguration("/**", configuration);
//        return source;
//    }
}
